<?php

namespace RPC;

class JsonRPC implements RPCInterface
{
    /**
     * Debug state
     *
     * @var boolean
     */
    private bool $debug;

    /**
     * The server URL
     *
     * @var string
     */
    private string $url;
    /**
     * If true, notifications are performed instead of requests
     *
     * @var boolean
     */
    private bool $notification = false;
    private $config;

    public function __construct()
    {
        $this->config = Config::get("rpc");
    }

    public function server(?object $object): bool
    {
        // TODO: Implement server() method.
        // checks if a JSON-RCP request has been received
        if (
            $_SERVER['REQUEST_METHOD'] != 'POST' ||
            empty($_SERVER['CONTENT_TYPE']) ||
            $_SERVER['CONTENT_TYPE'] != 'application/json'
        ) {
            // This is not a JSON-RPC request
            return false;
        }

        // reads the input data
        $request = json_decode(file_get_contents('php://input'),true);

        // executes the task on local object
        try {
            if ($result = @call_user_func_array(array($object,$request['method']),$request['params'])) {
                $response = array (
                    'id' => $request['id'],
                    'result' => $result,
                    'error' => NULL
                );
            } else {
                $response = array (
                    'id' => $request['id'],
                    'result' => NULL,
                    'error' => 'unknown method or incorrect parameters'
                );
            }
        } catch (Exception $e) {
            $response = array (
                'id' => $request['id'],
                'result' => NULL,
                'error' => $e->getMessage()
            );
        }

        // output the response
        if (!empty($request['id'])) { // notifications don't want response
            header('content-type: text/javascript');
            echo json_encode($response);
        }

        // finish
        return true;
    }

    public function client(?string $object, ?array $params = NULL)
    {
        $this->url = $this->config['host'].'/'.$object;
        // debug state
        empty($debug) ? $this->debug = false : $this->debug = true;
        // message id
        $id = 1;
        // TODO: Implement client() method.
        // check
        if (!is_scalar($object)) {
            throw new Exception('Method name has no scalar value');
        }

        // check
        if (is_array($params)) {
            // no keys
            $params = array_values($params);
        } else {
            throw new Exception('Params must be given as array');
        }

        // sets notification or request task
        if ($this->notification) {
            $currentId = NULL;
        } else {
            $currentId = $id;
        }

        // prepares the request
        $request = array(
            'method' => $object,
            'params' => $params,
            'id' => $currentId
        );
        $request = json_encode($request);
        $debug = '';
        $this->debug && $debug.='***** Request *****'."\n".$request."\n".'***** End Of request *****'."\n\n";

        // performs the HTTP POST
        $opts = array ('http' => array (
            'method'  => 'POST',
            'header'  => 'Content-type: application/json',
            'content' => $request
        ));
        $context  = stream_context_create($opts);
        if ($fp = fopen($this->url, 'r', false, $context)) {
            $response = '';
            while($row = fgets($fp)) {
                $response.= trim($row)."\n";
            }
            $this->debug && $this->debug.='***** Server response *****'."\n".$response.'***** End of server response *****'."\n";
            $response = json_decode($response,true);
        } else {
            throw new Exception('Unable to connect to '.$this->url);
        }

        // debug output
        if ($this->debug) {
            echo nl2br($debug);
        }

        // final checks and return
        if (!$this->notification) {
            // check
            if ($response['id'] != $currentId) {
                throw new Exception('Incorrect response id (request id: '.$currentId.', response id: '.$response['id'].')');
            }
            if (!is_null($response['error'])) {
                throw new Exception('Request error: '.$response['error']);
            }

            return $response['result'];

        } else {
            return true;
        }
    }

    public function setOpt(array|string|null $options): JsonRPC
    {
        // TODO: Implement setOpt() method.
        empty($options) ? $this->notification = false : $this->notification = true;
        return $this;
    }

    public function getOpt(int|string $type)
    {
        // TODO: Implement getOpt() method.
    }
}
